﻿namespace Pub.Class
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Threading;

    public static class FuncExtensions
    {
        public static Func<T, TResult> Fix<T, TResult>(this Func<Func<T, TResult>, Func<T, TResult>> f)
        {
            return x => f(f.Fix<T, TResult>())(x);
        }

        public static Func<T1, T2, TResult> Fix<T1, T2, TResult>(this Func<Func<T1, T2, TResult>, Func<T1, T2, TResult>> f)
        {
            return (x, y) => f(f.Fix<T1, T2, TResult>())(x, y);
        }

        public static IEnumerable<TResult> Map<T, TResult>(this Func<T, TResult> f, IEnumerable<T> source)
        {
            return source.Select<T, TResult>(f);
        }

        public static T Retry<T>(this Func<T> action, int numRetries, int retryTimeout, bool throwIfFail, Action onFailureAction)
        {
            if (action.IsNull())
            {
                throw new ArgumentNullException("action");
            }
            T local = default(T);
            do
            {
                try
                {
                    return action();
                }
                catch
                {
                    if (onFailureAction.IsNotNull())
                    {
                        onFailureAction();
                    }
                    if ((numRetries <= 0) && throwIfFail)
                    {
                        throw;
                    }
                    if (retryTimeout > 0)
                    {
                        Thread.Sleep(retryTimeout);
                    }
                }
            }
            while (numRetries-- > 0);
            return local;
        }

        public static Func<T1, TResult> TailFix<T1, TResult>(this Func<Func<T1, TResult>, T1, TResult> f)
        {
            return delegate (T1 p1) {
                bool callback = false;
                Func<T1, TResult> func = delegate (T1 q1) {
                    p1 = q1;
                    callback = true;
                    return default(TResult);
                };
                while (true)
                {
                    TResult local = f(func, p1);
                    if (!callback)
                    {
                        return local;
                    }
                    callback = false;
                }
            };
        }

        public static Func<T1, T2, TResult> TailFix<T1, T2, TResult>(this Func<Func<T1, T2, TResult>, T1, T2, TResult> f)
        {
            return delegate (T1 p1, T2 p2) {
                bool callback = false;
                Func<T1, T2, TResult> func = delegate (T1 q1, T2 q2) {
                    p1 = q1;
                    p2 = q2;
                    callback = true;
                    return default(TResult);
                };
                while (true)
                {
                    TResult local = f(func, p1, p2);
                    if (!callback)
                    {
                        return local;
                    }
                    callback = false;
                }
            };
        }

        public static Func<T1, T2, T3, TResult> TailFix<T1, T2, T3, TResult>(this Func<Func<T1, T2, T3, TResult>, T1, T2, T3, TResult> f)
        {
            return delegate (T1 p1, T2 p2, T3 p3) {
                bool callback = false;
                Func<T1, T2, T3, TResult> func = delegate (T1 q1, T2 q2, T3 q3) {
                    p1 = q1;
                    p2 = q2;
                    p3 = q3;
                    callback = true;
                    return default(TResult);
                };
                while (true)
                {
                    TResult local = f(func, p1, p2, p3);
                    if (!callback)
                    {
                        return local;
                    }
                    callback = false;
                }
            };
        }
    }
}

